/* Autostop
*
* Copyright (c) Maciej Gajewski, Grzegorz Latarowski
* http://code.google.com/p/autostop-automaty-drogowe
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. NO WARRANTY OF ANY KIND is expressed or implied.
*/

// libc
#include <stdio.h>

// stl
#include <string>
#include <vector>	// required by ocrad
using namespace std;

// zebra
#include <zebra.h>
using namespace zebra;

// datamatrix
extern "C" {
#include <dmtx.h>
}

// ocrad
#include "common.h"
#include "rational.h"
#include "rectangle.h"
#include "page_image.h"
#include "textpage.h"

// Qt
#include <QCoreApplication>
#include <QThread>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDataStream>
#include <QDebug>
#include <QTime>

// local
#include "camera.h"

// globals

quint16 	port	= 5102;							///< tcp port

// types

/// Requst type
enum Requests
{
	RequestY 			= 1,
	RequestSegmented	= 2,
	RequestBarcode		= 3,
	DataMatrix			= 4,
	Ocr					= 5
};

// ========================= barcode decoder classes =============

// ================ pixel handler ===========
class PixelHandler : public ImageWalker::Handler
{
public:
	PixelHandler() : pScanner( NULL ) {}

	virtual char walker_callback (ImageWalker &walker, void *pixel)
	{
		if ( pScanner )
		{
			// start over when new line started
			if(!walker.get_col() || !walker.get_row())
			{
				pScanner->new_scan();
			}
			int y = *((char*) pixel);
			pScanner->scan_y( y * 0x100 ); // * 0x100 to scale to internal 
			
		}
		else
		{
			qWarning("pixel handler without scanner");
		}
		return 0;
	}
	
	Scanner* pScanner;
};

// ================ symbol handler ===========
class SymbolHandler : public Decoder::Handler
{
public:
	virtual void decode_callback (Decoder &decoder)
	{
		if(decoder.get_type() > ZEBRA_PARTIAL)
		{
			QString type = decoder.get_symbol_name();
			QString code = decoder.get_data_string().c_str();
			
			result = type + " " + code;
		}
	}
	
	QString result;
};

// ================================= barcode decoder function ==========
/// input - image data and size
/// output - decode barcode as string, or null string if undoable
QString readBarcode( const QByteArray& data, int width, int height )
{
	// barcode reder stuff
	Decoder		decoder;
	Scanner		scanner( decoder );
	ImageWalker walker;

	PixelHandler	pixelHandler;
	SymbolHandler	symbolHandler;
	pixelHandler.pScanner = &scanner;
	
	// init
    decoder.set_handler( symbolHandler );
    walker.set_handler( pixelHandler );

	walker.set_size(width, height);
	walker.set_stride( 1, width ); // bytes per col, bytes per row
	scanner.new_scan(); //
	
	walker.walk( data.data() );
	
	return symbolHandler.result;
}


// ================================= ocr ==========
/// input - image data and size
/// output - readed text
QString ocr( const QByteArray& data, int width, int height )
{
	// memory buffer for output text
	char buffer[ 2048 ] = "";
	
	// open memory stream
	FILE* stream = fmemopen( buffer, 2048, "w" );
	if ( ! stream )
	{
		qWarning("Failed to open memory stream");
		return QString();
	}
	
	try
	{
	// create image
	Page_image image( (const unsigned char*)data.data(), width, height, Rational( -1 ), false );
	
	image.analyse_layout( 0 ); // TODO I dont know if this is neccesary
	
	image.adapt_thresholds(); // auto-treshold
	
	// create control
	Control ctrl;
	ctrl.outfile = stream;
	ctrl.format = Control::utf8;
	
	
	// create page
	Textpage page( image, "", ctrl );
	
	// catch errors
	} catch ( ... )
	{
		qWarning("Ocrad error");
	}
	
	// cleanup
	fclose( stream );
	
	return buffer;
}

// ================================= 2D barcode decoder function ==========
/// input - image data and size
/// output - decode barcode as string, or null string if undoable
QString readDataMatrix( const QByteArray& data, int width, int height )
{
	int scanWidth = 150;
	int scanHeight = 150;
	int scanX = 245;
	int scanY = 215;
	
	qDebug("reading data matrix");
	DmtxDecode* pDecode = dmtxDecodeStructCreate();
	pDecode->option = DmtxSingleScanOnly; // speedup?
	
	dmtxImageInit( &(pDecode->image) );
	
	// populate image
	pDecode->image.width = scanWidth;
	pDecode->image.height = scanHeight;
	pDecode->image.pxl = (DmtxPixel*)malloc( sizeof(DmtxPixel) * scanWidth * scanHeight );
	
	qDebug("populating image");

	for( int y = 0; y < scanHeight; y++ )
	for( int x = 0 ; x < scanWidth; x++ )
	{
		
		unsigned char Y = data[ width * (y+scanY) + (x+scanX)  ]; 
		pDecode->image.pxl[scanWidth*y+x].R = Y;
		pDecode->image.pxl[scanWidth*y+x].G = Y;
		pDecode->image.pxl[scanWidth*y+x].B = Y;
	}

	// scan image
	dmtxScanStartNew( pDecode );

	qDebug("scaning columns");
	// scan each column
	for( int col = 0; col < scanWidth; col+=2 )
	{
		dmtxScanLine( pDecode, DmtxDirUp, col );
	}
	qDebug("scaning rows");
	// scan each row
	for( int row = 0; row < scanHeight; row+=2 )
	{
		dmtxScanLine( pDecode, DmtxDirRight, row );
	}

	// read matrices
	QString result;
	int codes = dmtxDecodeGetMatrixCount( pDecode );
	qDebug("readed %d codes", codes);
	for( int m = 0; m < codes; m++ )
	{
		DmtxMatrixRegion *pMatrixRegion	= dmtxDecodeGetMatrix( pDecode, m );
		result += QString( (const char*)pMatrixRegion->output ) + " ";
	}
	
	// clear
	dmtxImageDeInit( &(pDecode->image) );
	dmtxDecodeStructDestroy( & pDecode );
	
	return result;
}	

// =============================== thread class ===================
class ServerThread : public QThread
{
private:
	int	_socket;	///< socket handle
	
public:

	// ---------------------- constructor ---------------------------
	ServerThread( int socket, QObject* parent ) : QThread( parent ), _socket( socket )
	{
	}
	virtual ~ServerThread() {}

	// ---------------------------- run --------------------------------
	virtual void run()
	{
		QTcpSocket socket;
		socket.setSocketDescriptor( _socket );
		// say something
		qDebug("Incomming connection from %s", qPrintable( socket.peerAddress().toString() ) );

		// init
		int imagewidth =  camera_image_width();
		int imageheight = camera_image_height();
		
		if ( imageheight <= 0 || imagewidth <= 0 )
		{
			qDebug("image size invalid");
			return;
		}
		QByteArray buffer;
		buffer.reserve( imagewidth* imageheight );
		QByteArray outBuffer;
		outBuffer.reserve( imagewidth* imageheight + 100 ); // roughly 100 bytes for extra data

		QString lastBarcode; // NOTE: barcode needs filtering

		// serve:

		// wait for request
		forever
		{
			while ( socket.bytesAvailable() < 4 ) // four bytes required for request
			{
				//qDebug("waiting for data");
				if ( ! socket.waitForReadyRead( -1 ) )
				{
					qDebug("waiting for request failed: %s", qPrintable( socket.errorString() ) );
					socket.close();
					return; // stop
				}
			}
			
			// read request
			quint32 request = 0;
			
			QDataStream stream( &socket );
			stream >> request;
			
			// get image from library
			uint64_t timestamp = 0;
			int width = 0;
			int height = 0;
			outBuffer.clear();
			
			// request full Y image, request = 1
			if ( request == RequestY || request == RequestBarcode 
				|| request == DataMatrix || request == Ocr )
			{
				width = imagewidth;
				height = imageheight;
				buffer.resize( width * height );
				int state = camera_get_image( (uint8_t*)buffer.data(), imagewidth * imageheight, &timestamp );
				if ( state != CAMERA_OK )
				{
					qDebug("error getting image: %s", camera_error_message() );
					return;
				}
			}
			// request segmented image, request = 2
			else if ( request == RequestSegmented )
			{
				width = imagewidth >> 3;
				height = imageheight >> 3;
				buffer.resize( width * height );
				int state = camera_get_segmented_image( (uint8_t*)buffer.data(), width * height, &timestamp );
				if ( state != CAMERA_OK )
				{
					qDebug("error getting segmented image: %s", camera_error_message() );
					return;
				}
			}
			else
			{
				qDebug("Unknown request: %d", request );
			}
			
			// write image
			QDataStream outStream( &outBuffer, QIODevice::WriteOnly );
			outStream << width;
			outStream << height;
			outStream << 0; // format: raw (1=jpeg2k)
			outStream << buffer;
			
			// if barcode requsted - try to decode
			if ( request == RequestBarcode  )
			{
				QString barcode = readBarcode( buffer, width, height );
				if ( lastBarcode == barcode && lastBarcode != "" ) // report barcode only if two subsequent reads equal
				{
					outStream << barcode;
				}
				else
				{
					outStream << QString();
				}
				
				if ( barcode != "" )
				{
					lastBarcode = barcode; // store for next frame
				}
			}
			// if ocr requsted - try to decode
			if ( request == Ocr  )
			{
				//QTime clock;
				//clock.start();
				QString text = ocr( buffer, width, height );
				//qDebug("OCR: %dms", clock.elapsed() );
				outStream << text;
			}
			// if datamatrix requsted - try to decode
			if ( request == DataMatrix )
			{
				QTime timer;
				timer.start();
				QString barcode = readDataMatrix( buffer, width, height );
				outStream << barcode;
				qDebug("Decoding datamatrix: %dms", timer.elapsed() );
			}
			
			// wrtie stream size
			stream << outBuffer.size();
			if ( ! socket.waitForBytesWritten( -1 ) )
			{
				qDebug("Error writing data: %s", qPrintable( socket.errorString() ) );
				return;
			}
			
			// wrtie out stream to socket
			int bytesWriten = 0;
			int bytesToWrite = outBuffer.size();
			while( bytesToWrite > 0 )
			{
				int w = socket.write( outBuffer.data() + bytesWriten, bytesToWrite );
				if ( ! socket.waitForBytesWritten( -1 ) )
				{
					qDebug("Error writing data: %s", qPrintable( socket.errorString() ) );
					return;
				}
				bytesToWrite -= w;
				bytesWriten += w;
			}
			
		} // forever
		
	}
	
};

// ============================== server class ====================
class Server : protected QTcpServer
{
public:

	Server() : QTcpServer() {}
	virtual ~Server() {}

	// ---------------- listen ----------------------
	/// Listen for incoming connection infinitely (or unitl error)
	void listen( quint16 port )
	{
		QTcpServer::listen( QHostAddress::Any, port );

		while ( waitForNewConnection(-1) )
		{
			// nothing, processing in incomingConnection()
		}
	}

	// ------------------- incoming connection -------
	virtual void incomingConnection( int socket )
	{
		ServerThread *thread = new ServerThread( socket, this );
		connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
		thread->start();
	}
};

// ================================ usage ================================
void usage()
{
	fprintf( stderr, "Simple server, listens on TCP port %d, on all interfaces\n", (int)port );
}

// ================================ main ================================-
int main( int argc, char** argv )
{
	QCoreApplication app( argc, argv );
	
	// create server
	Server server;

	// init camera
	int state = camera_init();
	if ( state != CAMERA_OK )
	{
		qDebug("Error initializing camera: %s", camera_error_message() );
		return 1;
	}
	camera_jpeg_set_shrink( 0 );
	camera_set_auto_exposure( 1 );
	camera_set_bgfactor( 0.01 );
	

	// listen, process
	qDebug("Starting test server: %s %s", __DATE__, __TIME__ );
	server.listen( port );

	return 0;
}



// eof

